package training.framework.persistence;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import training.framework.business.AbstractEntitySearchOptions;

public abstract class AbstractEntityDao<Identifier, Entity extends AbstractEntity<Identifier>, EntitySearchOptions extends AbstractEntitySearchOptions>{

	private EntityManagerFactory factory;
	private Class<Entity> entityClass;

	public AbstractEntityDao(EntityManagerFactory factory, Class<Entity> entityClass){
		if (factory == null){
			throw new IllegalArgumentException("factory == null");
		}
		if (entityClass == null){
			throw new IllegalArgumentException("entityClass == null");
		}
		this.factory = factory;
		this.entityClass = entityClass;
	}

	private void validateEntity(Entity entity){
		if (entity == null){
			throw new IllegalArgumentException("entity == null");
		}
	}
	
	public void insertEntity(final Entity entity) {
		validateEntity(entity);
		AbstractManagerTemplate template = new AbstractManagerTemplate() {			
			@Override
			protected void execute(EntityManager manager) {
				manager.persist(entity);
			}
		};
		template.execute(factory);
	}	

	public Entity selectEntity(Identifier identifier){
		if (identifier == null){
			throw new IllegalArgumentException("identifier == null");
		}
		EntityManager manager = null;
		try{
			manager = factory.createEntityManager();
			return manager.find(entityClass, identifier);

		}finally{

			if (manager != null){
				try {
					manager.close();
				} catch (Exception e) {

				}

			}
		}
	}


	private void validateOptions(EntitySearchOptions options){
		if (options == null){
			throw new IllegalArgumentException("options == null");
		}
	}


	public int selectEntityCount(EntitySearchOptions options) {		
		validateOptions(options);
		EntityManager manager = null;
		try {
			manager =  this.factory.createEntityManager();

			CriteriaBuilder builder = manager.getCriteriaBuilder();
			CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);

			Root<Entity> root = criteriaQuery.from(entityClass);

			criteriaQuery.select(builder.count(root));
			criteriaQuery.where(toPredicate(builder, root, options));

			TypedQuery<Long> query = manager.createQuery(criteriaQuery);

			Long result = query.getSingleResult();
			return result.intValue();
		} finally {
			if (manager != null) {
				try {
					manager.close();
				} catch (RuntimeException e) {}
			}
		}						
	}

	public List<Entity> selectEntity(EntitySearchOptions options) {
		validateOptions(options);
		EntityManager manager = null;
		try {
			manager =  this.factory.createEntityManager();

			CriteriaBuilder builder = manager.getCriteriaBuilder();
			CriteriaQuery<Entity> criteriaQuery = builder.createQuery(entityClass);
			Root<Entity> root = criteriaQuery.from(entityClass);

			criteriaQuery.select(root);
			criteriaQuery.where(toPredicate(builder, root, options));


			Order order = toOrder(builder, root);
			if (order != null){
				criteriaQuery.orderBy(order);
			}
			//builder.asc(root.get(Post_.title));


			TypedQuery<Entity> query = manager.createQuery(criteriaQuery);
			if (options.getFirstResult() != null) {
				query.setFirstResult(options.getFirstResult());
			}
			if (options.getMaxResults() != null) {
				query.setMaxResults(options.getMaxResults());
			}

			List<Entity> result = query.getResultList();
			return result;
		} finally {
			if (manager != null) {				
				try {
					manager.close();
				} catch (RuntimeException e) {}
			}
		}				
	}	




	protected abstract Order toOrder(CriteriaBuilder builder, Root<Entity> root);

	protected abstract Predicate toPredicate(CriteriaBuilder builder, Root<Entity> root, EntitySearchOptions options);

	protected String toLikeParameter(String parameter) {
		return "%" + parameter.toLowerCase().trim() + "%";
	}

	public void updateEntity(final Entity entity) {
		validateEntity(entity);
		AbstractManagerTemplate template = new AbstractManagerTemplate() {			
			@Override
			protected void execute(EntityManager manager) {
				manager.merge(entity);
			}
		};
		template.execute(this.factory);
	}	

	public void deleteEntity(final Entity entity) {
		validateEntity(entity);
		if (entity.getIdentifier() == null){
			throw new IllegalArgumentException("entity.getIdentifier() == null");
		}
		AbstractManagerTemplate template = new AbstractManagerTemplate() {			
			@Override
			protected void execute(EntityManager manager) {
				Entity databasePost = manager.find(entityClass, entity.getIdentifier());
				manager.remove(databasePost);	
			}
		};
		template.execute(this.factory);		
	}
}
